'use strict'; const { Document } = require('/document.js'); const { PixelReaderRGBA8 } = require('/pixelaccessor.js'); const { ArtTextNodeDefinition, ContainerNodeDefinition, NodeChildType } = require('/nodes.js'); const { StoryBuilder } = require('/storybuilder.js'); const { StoryDelta, GlyphAttDoubleType } = require('/storydelta.js'); const { FillDescriptor } = require('/fills.js'); const { RGBA8 } = require('/colours.js'); const { Transform } = require('/geometry.js'); const { FontFamily } = require('/fonts.js'); const { AddChildNodesCommandBuilder, DocumentCommand, CompoundCommandBuilder, NodeMoveType } = require('/commands.js'); const { Selection } = require('/selections.js'); const { Dialog, DialogResult } = require('/dialog.js'); const { UnitType } = require('/units.js'); const { app } = require('/application.js'); function abort(msg) { app.alert(msg); throw new Error(msg); } function isOk(r) { return (r?.value ?? r) === DialogResult.Ok.value; } const doc = Document.current; if (!doc) abort('No active document.'); const spread = doc.currentSpread; // Find first raster/image node recursively function findRasterNode(children) { for (let i = 0; i < children.length; i++) { const n = children.at(i); if (n.isRasterNode || n.isImageNode) return n; if (n.children && n.children.length > 0) { const found = findRasterNode(n.children); if (found) return found; } } return null; } const rasterNode = findRasterNode(spread.children); if (!rasterNode) abort('No raster or image layer found.\nPlease ensure a pixel or image layer exists on the spread.'); const patterns = ["none","average","negative","red","green","blue","average 1","average 2","average 3","average 4","multiple","threshold","threshold 1","random","random 1","random 2"]; // ---- Dialog ---- const dlg = Dialog.create('Make Text'); const col = dlg.addColumn(); const g1 = col.addGroup('Text to place'); const edtText = g1.addTextBox('Text to place', 'A'); const edtCount = g1.addUnitValueEditor('Number of items', UnitType.Number, UnitType.Number, 500, 1, 100000); const edtMinSize = g1.addUnitValueEditor('Min font size (pt)', UnitType.Number, UnitType.Number, 10, 1, 10000); const edtMaxSize = g1.addUnitValueEditor('Max font size (pt)', UnitType.Number, UnitType.Number, 200, 1, 10000); const edtRotMin = g1.addUnitValueEditor('Rotation min (°)', UnitType.Degree, UnitType.Degree, 0, -360, 360); const edtRotMax = g1.addUnitValueEditor('Rotation max (°)', UnitType.Degree, UnitType.Degree, 0, -360, 360); const edtSizMin = g1.addUnitValueEditor('Min threshold (0.1–255)', UnitType.Number, UnitType.Number, 0.1, 0.1, 255).setPrecision(2); const edtSizMax = g1.addUnitValueEditor('Max threshold (0.1–255)', UnitType.Number, UnitType.Number, 255, 0.1, 255).setPrecision(2); const edtRedF = g1.addUnitValueEditor('Red boost (0.1–10)', UnitType.Number, UnitType.Number, 1, 0.1, 10).setPrecision(2); const edtGreenF = g1.addUnitValueEditor('Green boost (0.1–10)', UnitType.Number, UnitType.Number, 1, 0.1, 10).setPrecision(2); const edtBlueF = g1.addUnitValueEditor('Blue boost (0.1–10)', UnitType.Number, UnitType.Number, 1, 0.1, 10).setPrecision(2); const edtPattern = g1.addComboBox('Pattern', patterns, 0); const edtFont = g1.addTextBox('Font name', 'Arial'); const rot = g1.addSwitch("Rotate based on font size", false); dlg.initialWidth = 420; const result = dlg.runModal(); if (!isOk(result)) abort('Cancelled.'); // We have the MODAL results now // Use first font in the document, fall back to first system font const docFontNames = []; doc.enumerateFontNames(name => { docFontNames.push(name); return true; }); const targetFontName = docFontNames.length > 0 ? docFontNames[0] : FontFamily.all[0].name; //const family = FontFamily.all.find(f => f.name === targetFontName) || FontFamily.all[0]; const family = FontFamily.all.find(f => f.name === edtFont.text) || FontFamily.all[0]; const currentFont = family.getFont(0); console.log(`Using font: ${family.name} / ${currentFont.postscriptName}`); // Read values const textStr = edtText.text || 'A'; const count = Math.round(edtCount.value); let minSizePt = edtMinSize.value, maxSizePt = edtMaxSize.value; if (minSizePt > maxSizePt) { const t = minSizePt; minSizePt = maxSizePt; maxSizePt = t; } let rotMin = edtRotMin.value, rotMax = edtRotMax.value; if (rotMin > rotMax) { const t = rotMin; rotMin = rotMax; rotMax = t; } let sizMin = edtSizMin.value, sizMax = edtSizMax.value; if (sizMin > sizMax) { const t = sizMin; sizMin = sizMax; sizMax = t; } const useRotation = rotMin !== 0 || rotMax !== 0; const pxPerPt = doc.dpi / 72; doc.executeCommand(DocumentCommand.createSetCurrentSpread(spread)); const rw = rasterNode.rasterWidth; const rh = rasterNode.rasterHeight; const bb = rasterNode.getSpreadBaseBox(false); const ri = rasterNode.rasterInterface; const bitmap = ri.createCompatibleBitmap(true); const reader = PixelReaderRGBA8.create(bitmap); // Place all text nodes const placeBuilder = AddChildNodesCommandBuilder.create(); placeBuilder.setInsertionTarget(spread); for (let i = 0; i < count; i++) { const px = Math.floor(Math.random() * rw); const py = Math.floor(Math.random() * rh); const pixel = reader.readPixel(px, py); const red1 = pixel.r; const green1 = pixel.g; const blue1 = pixel.b; let avg = (red1 + green1 + blue1) / 3; switch (edtPattern.selectedIndex) { case 0: avg = 255; break; case 1: break; case 2: avg = 255 - avg; break; case 3: avg = red1; break; case 4: avg = green1; break; case 5: avg = blue1; break; case 6: avg = (red1 * 2 + green1 + blue1) / 6; break; case 7: avg = red1 + green1 + blue1; break; case 8: avg = 255 - (red1 + green1 + blue1); break; case 9: avg = red1 + avg; break; case 10: avg = (red1 * green1 * blue1) / (255 * 255 * 255); break; case 11: avg = (avg > 125) ? 255 : 0; break; case 12: avg = (avg < 125) ? 255 : 0; break; case 13: avg = 255*Math.random(); break; case 14: avg = 125*Math.random(); break; case 15: avg = 40*Math.random(); break; } if (avg > sizMax) avg = sizMax; if (avg < sizMin) avg = sizMin; // Scale font size by avg factor const sizeFactor = avg / 255; let fs1 = minSizePt + Math.random() * (maxSizePt - minSizePt); const fontSize = fs1 * sizeFactor * pxPerPt; const pcx = bb.x + (px / rw) * bb.width; const pcy = bb.y + (py / rh) * bb.height; const sb = StoryBuilder.create(); sb.setToArtisticTextDefaultStyle(doc.dpi, doc.format); sb.applyGlyphDelta(StoryDelta.createGlyphDouble(GlyphAttDoubleType.Height, fontSize)); sb.applyGlyphDelta(StoryDelta.createPostscriptName(currentFont.postscriptName)); sb.applyGlyphDelta(StoryDelta.createBrushFill( FillDescriptor.createSolid(RGBA8(pixel.r * edtRedF.value, pixel.g * edtGreenF.value, pixel.b * edtBlueF.value, pixel.alpha)) )); sb.addText(textStr); placeBuilder.addNode(ArtTextNodeDefinition.createFromStoryBuilder({ x: pcx, y: pcy }, sb)); } const placeCmd = placeBuilder.createCommand(false, NodeChildType.Main); doc.executeCommand(placeCmd); const newNodes = []; placeCmd.newNodes.forEach(n => newNodes.push(n)); console.log(`Placed ${newNodes.length} text nodes`); // Apply rotation if needed if (useRotation) { const compound = CompoundCommandBuilder.create(); newNodes.forEach(node => { let rotRad = (rotMin + Math.random() * (rotMax - rotMin)) * Math.PI / 180; const nodeBb = node.getSpreadBaseBox(false); const originX = nodeBb.x + nodeBb.width / 2; const originY = nodeBb.y + nodeBb.height / 2; let scaledRot = rotRad; if (rot.value) {scaledRot = rotRad * Math.min(1, nodeBb.height / edtMaxSize.value);} let combined = Transform.createTranslate(-originX, -originY); combined = Transform.createRotate(scaledRot).multiply(combined); combined = Transform.createTranslate(originX, originY).multiply(combined); compound.addCommand(DocumentCommand.createTransform( Selection.create(doc, node), combined, { mergeable: false } )); }); doc.executeCommand(compound.createCommand()); } // Group into a container doc.executeCommand(DocumentCommand.createSetCurrentSpread(spread)); const grpBuilder = AddChildNodesCommandBuilder.create(); grpBuilder.setInsertionTarget(spread); grpBuilder.addContainerNode(ContainerNodeDefinition.create('Make Text Group')); const grpCmd = grpBuilder.createCommand(false, NodeChildType.Main); doc.executeCommand(grpCmd); const grpNodes = []; grpCmd.newNodes.forEach(n => grpNodes.push(n)); const container = grpNodes[0]; const moveCompound = CompoundCommandBuilder.create(); newNodes.forEach(node => { moveCompound.addCommand( DocumentCommand.createMoveNodes( Selection.create(doc, node), container, NodeMoveType.Inside, NodeChildType.Main ) ); }); doc.executeCommand(moveCompound.createCommand()); console.log('Done! All text nodes grouped.');